/*
 * Copyright (C) 2020 xuexiangjys(xuexiangjys@163.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.xuexiang.xui_lib.component.picker;


import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.utils.Color;
import ohos.agp.utils.Rect;
import ohos.app.Context;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.multimodalinput.event.TouchEvent;

import com.xuexiang.xui_lib.ResourceTable;
import com.xuexiang.xui_lib.util.AttrValue;
import com.xuexiang.xui_lib.util.SizeUtils;

import java.io.InputStream;
import java.util.HashSet;
import java.util.Set;

/**
 * 滑块选择器
 *
 * @since 2020-01-04
 */
public class XSeekBar extends Component implements Component.EstimateSizeListener,
    Component.DrawTask, Component.TouchEventListener {

    /**
     * 数字改变回调
     */
    public interface OnSeekBarListener {
        /**
         * 数字改变回调
         *
         * @param seekBar 控件
         * @param newValue 新数值
         */
        void onValueChanged(XSeekBar seekBar, int newValue);
    }

    private static int DEFAULT_TOUCH_TARGET_SIZE;
    private static int DEFAULT_TEXT_MIN_SPACE;
    private static final int DEFAULT_MAX = 100;
    /**
     * 刻度的宽度参数
     */
    private static final float DEFAULT_BIG_SCALE_WITH = 1.7f;
    private static final float DEFAULT_MIDDLE_SCALE_WITH = 1.2f;
    private static final float DEFAULT_SMALL_SCALE_WITH = 1.0f;

    private final Paint mPaint = new Paint();
    private int mLineStartX;
    private int mLineEndX;
    private int mLineLength;
    private int mMaxPosition = 0;
    private int mRange;
    private int mMiddleY = 0;
    private Rect mMaxTextRect = new Rect();
    private Rect mRulerTextRect = new Rect();
    /**
     * List of event IDs touching targets
     */
    private Set<Integer> mTouchingMinTarget = new HashSet<>();
    private Set<Integer> mTouchingMaxTarget = new HashSet<>();
    private boolean mIsTouching = false;
    private boolean mIsLastTouchedMin;
    private int mIsSelectedNumber = -1;
    private boolean mIsFirstInit = true;
    private float mConvertFactor;
    private OnSeekBarListener mOnSeekBarListener;

    // ========属性========== //

    private int mVerticalPadding;
    private Color mInsideRangeColor;
    private Color mOutsideRangeColor;
    private float mInsideRangeLineStrokeWidth;
    private float mOutsideRangeLineStrokeWidth;
    private int mMax = DEFAULT_MAX;
    private int mMin = 0;
    private PixelMap mSliderIcon;

    private PixelMapHolder mSliderpixelMapHolder;

    private PixelMap mSliderIconFocus;
    private PixelMapHolder mSliderFocuspixelMapHolder;

    private boolean mIsLineRound;

    private boolean mIsShowBubble;
    private PixelMap mBubbleBitmap;
    private PixelMapHolder pixelMapHolder;
    private boolean mIsShowNumber;
    private Color mNumberTextColor;
    private int mNumberTextSize;
    private float mNumberMarginBottom;

    private boolean mIsShowRuler;
    private Color mRulerColor;
    private Color mRulerTextColor;
    private int mRulerTextSize;
    private int mRulerMarginTop;
    private int mRulerDividerHeight;
    private int mRulerTextMarginTop;
    private int mRulerInterval;

    /**
     * 初始化
     *
     * @param context
     */
    public XSeekBar(Context context) {
        this(context, null);
    }

    /**
     * 初始化
     *
     * @param context
     * @param attrs
     */
    public XSeekBar(Context context, AttrSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * 初始化
     *
     * @param context
     * @param attrs
     * @param defStyleAttr
     */
    public XSeekBar(Context context, AttrSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttrs(context, attrs, defStyleAttr);
        setEstimateSizeListener(this);
        addDrawTask(this);
        setTouchEventListener(this);
    }

    private void initAttrs(Context context, AttrSet attrs, int defStyleAttr) {
        DEFAULT_TOUCH_TARGET_SIZE = SizeUtils.fp2px(context, 20);
        DEFAULT_TEXT_MIN_SPACE = SizeUtils.fp2px(context, 2);
        Color colorAccent = AttrValue.get(attrs, "colorAccent", new Color(Color.getIntColor("#299EE3")));
        Color colorControlNormal = AttrValue.get(attrs, "colorControlNormal", new Color(Color.getIntColor("#BEC2C7")));
        if (attrs != null) {
            mVerticalPadding = AttrValue.getDimension(attrs, "xsb_verticalPadding", SizeUtils.fp2px(context, 10));

            // 滑条
            mInsideRangeColor = AttrValue.get(attrs, "xsb_insideRangeLineColor", colorAccent);
            mOutsideRangeColor = AttrValue.get(attrs, "xsb_outsideRangeLineColor", new Color(Color.getIntColor("#F0F0F0")));
            mInsideRangeLineStrokeWidth = AttrValue.getDimension(attrs, "xsb_insideRangeLineStrokeWidth", SizeUtils.fp2px(context, 4));
            mOutsideRangeLineStrokeWidth = AttrValue.getDimension(attrs, "xsb_outsideRangeLineStrokeWidth", SizeUtils.fp2px(context, 4));
            mMin = AttrValue.get(attrs, "xsb_min", mMin);
            mSliderIcon = getPixelMap(ResourceTable.Media_xui_ic_slider_icon);
            mSliderIconFocus = getPixelMap(ResourceTable.Media_xui_ic_slider_icon);
            mIsLineRound = AttrValue.get(attrs, "xsb_isLineRound", true);

            // 气泡
            mIsShowBubble = AttrValue.get(attrs, "xsb_isShowBubble", false);
            boolean isFitColor = AttrValue.get(attrs, "xsb_isFitColor", true);

            mIsShowNumber = AttrValue.get(attrs, "xsb_isShowNumber", true);

            mNumberTextColor = AttrValue.get(attrs, "xsb_numberTextColor", colorAccent);

            mNumberTextSize = AttrValue.getDimension(attrs, "xsb_numberTextSize", SizeUtils.fp2px(context, 12));
            mNumberMarginBottom = AttrValue.getDimension(attrs, "xsb_numberMarginBottom", SizeUtils.fp2px(context, 2));

            if (isFitColor) {
                if (mIsShowBubble) {
                    mNumberTextColor = Color.WHITE;
                }
                mBubbleBitmap = getPixelMap(ResourceTable.Media_xui_bg_bubble_gree);
            } else {
                mBubbleBitmap = getPixelMap(ResourceTable.Media_xui_bg_bubble_blue);
            }

            // 刻度尺

            mIsShowRuler = AttrValue.get(attrs, "xsb_isShowRuler", false);
            mRulerColor = AttrValue.get(attrs, "xsb_rulerColor", colorControlNormal);
            mRulerTextColor = AttrValue.get(attrs, "xsb_rulerTextColor", colorControlNormal);
            mRulerTextSize = AttrValue.getDimension(attrs, "xsb_rulerTextSize", SizeUtils.fp2px(context, 12));
            mRulerMarginTop = AttrValue.getDimension(attrs, "xsb_rulerMarginTop", SizeUtils.fp2px(context, 4));
            mRulerDividerHeight = AttrValue.getDimension(attrs, "xsb_rulerDividerHeight", SizeUtils.fp2px(context, 4));
            mRulerTextMarginTop = AttrValue.getDimension(attrs, "xsb_rulerTextMarginTop", SizeUtils.fp2px(context, 4));
            mRulerInterval = AttrValue.get(attrs, "xsb_rulerInterval", 20);
        }
        mRange = mMax - mMin;
    }

    private PixelMap getPixelMap(int resId) {
        InputStream drawableInputStream = null;
        try {
            drawableInputStream = getResourceManager().getResource(resId);
            ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions();
            sourceOptions.formatHint = "image/png";
            ImageSource imageSource = ImageSource.create(drawableInputStream, sourceOptions);
            ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
            PixelMap pixelMap = imageSource.createPixelmap(decodingOptions);
            return pixelMap;
        } catch (Exception e) {
            e.getLocalizedMessage();
        } finally {
            try {
                if (drawableInputStream != null) {
                    drawableInputStream.close();
                }
            } catch (Exception e) {
                e.getLocalizedMessage();
            }
        }
        return null;
    }

    private void drawEntireRangeLine(Canvas canvas) {
        mPaint.setColor(mOutsideRangeColor);
        mPaint.setStrokeWidth(mOutsideRangeLineStrokeWidth);
        canvas.drawLine(mLineStartX, mMiddleY, mLineEndX, mMiddleY, mPaint);

        if (mIsLineRound) {
            mPaint.setColor(mInsideRangeColor);
            canvas.drawCircle(mLineStartX, mMiddleY, mOutsideRangeLineStrokeWidth / 2, mPaint);
            mPaint.setColor(mOutsideRangeColor);
            canvas.drawCircle(mLineEndX, mMiddleY, mOutsideRangeLineStrokeWidth / 2, mPaint);
        }
    }

    private void drawSelectedRangeLine(Canvas canvas) {
        mPaint.setStrokeWidth(mInsideRangeLineStrokeWidth);
        mPaint.setColor(mInsideRangeColor);
        canvas.drawLine(mLineStartX, mMiddleY, mMaxPosition, mMiddleY, mPaint);
    }

    private void drawSelectedNumber(Canvas canvas) {
        String max = String.valueOf(getSelectedNumber());
        mMaxTextRect = getTextBounds(max);
        float yText;

        // bubble
        if (mIsShowBubble) {
            float top = mMiddleY - mSliderIcon.getImageInfo().size.height / 2F - mBubbleBitmap.getImageInfo().size.height - mNumberMarginBottom;
            yText = top + mBubbleBitmap.getImageInfo().size.height / 2F + mMaxTextRect.getHeight() / 2F - 6;
            pixelMapHolder = new PixelMapHolder(mBubbleBitmap);
            canvas.drawPixelMapHolder(pixelMapHolder, mMaxPosition - mBubbleBitmap.getImageInfo().size.width / 2F, top, mPaint);
        } else {
            yText = mMiddleY - mSliderIcon.getImageInfo().size.height / 2F - mNumberMarginBottom;
        }

        // text
        float maxX = mMaxPosition - mMaxTextRect.getWidth() / 2F;
        mPaint.setTextSize(mNumberTextSize);
        mPaint.setColor(mNumberTextColor);
        canvas.drawText(mPaint, max, maxX, yText);
    }

    private void drawRuler(Canvas canvas) {
        if (mIsShowRuler) {
            float startX = mLineStartX;
            float stopY = 0;
            float startY = 0;
            float divider = (float) mRulerInterval / 10f;
            float scaleLength = (float) mLineLength / ((mMax - mMin) / divider) / divider;

            boolean isMinHasText = false;
            boolean isMaxHasText = false;

            for (int i1 = mMin; i1 <= mMax; i1++) {
                if (i1 % mRulerInterval == 0) {

                    // draw big scale
                    startY = mMiddleY + mSliderIcon.getImageInfo().size.height / 2F + mRulerMarginTop;
                    stopY = startY + mRulerDividerHeight * 3;

                    mPaint.setColor(mRulerTextColor);
                    mPaint.setTextSize(mRulerTextSize);
                    mRulerTextRect = getRulerTextBounds(String.valueOf(i1));
                    canvas.drawText(mPaint, String.valueOf(i1), startX - mRulerTextRect.getWidth() / 2F, stopY + mRulerTextRect.getHeight() + mRulerTextMarginTop);
                    if (i1 == mMin) {
                        isMinHasText = true;
                    }
                    if (i1 == mMax) {
                        isMaxHasText = true;
                    }
                    mPaint.setStrokeWidth(DEFAULT_BIG_SCALE_WITH);
                    mPaint.setColor(mRulerColor);
                    canvas.drawLine(startX, startY, startX, stopY, mPaint);
                } else if (i1 % (mRulerInterval / 2) == 0 && mRulerInterval % 10 == 0) {

                    // draw middle scale
                    startY = mMiddleY + mSliderIcon.getImageInfo().size.height / 2F + mRulerMarginTop;
                    stopY = startY + mRulerDividerHeight * 2;
                    mPaint.setStrokeWidth(DEFAULT_MIDDLE_SCALE_WITH);
                    mPaint.setColor(mRulerColor);
                    canvas.drawLine(startX, startY, startX, stopY, mPaint);
                } else {
                    // draw small scale
                    startY = mMiddleY + mSliderIcon.getImageInfo().size.height / 2F + mRulerMarginTop;
                    stopY = startY + mRulerDividerHeight;
                    mPaint.setStrokeWidth(DEFAULT_SMALL_SCALE_WITH);
                    if (i1 % (mRulerInterval / 10) == 0) {
                        mPaint.setColor(mRulerColor);
                        canvas.drawLine(startX, startY, startX, stopY, mPaint);
                    }
                }
                if ((i1 == mMax && !isMaxHasText) || (i1 == mMin && !isMinHasText)) {
                    mPaint.setColor(mRulerTextColor);
                    mPaint.setTextSize(mRulerTextSize);
                    mRulerTextRect = getRulerTextBounds(String.valueOf(i1));
                    float x = startX - mRulerTextRect.getWidth() / 2F;

                    // 修正最大值与最小值文本与满刻度文本太靠近时显示重叠问题
                    if (i1 == mMax && i1 % mRulerInterval == 1) {
                        x = startX + DEFAULT_TEXT_MIN_SPACE;
                    }
                    if (i1 == mMin && i1 % mRulerInterval == mRulerInterval - 1) {
                        x = startX - mRulerTextRect.getWidth() / 2F - DEFAULT_TEXT_MIN_SPACE;
                    }
                    canvas.drawText(mPaint, String.valueOf(i1), x, startY + mRulerDividerHeight * 3 + mRulerTextRect.getHeight() + mRulerTextMarginTop);
                }
                startX += scaleLength;
            }
        }
    }

    private void drawSelectedTargets(Canvas canvas) {
        mPaint.setColor(mInsideRangeColor);

        // DensityUtils.dp2px(3)
        canvas.drawCircle(mMaxPosition, mMiddleY, 3, mPaint);
        if (!mIsTouching) {
            mSliderpixelMapHolder = new PixelMapHolder(mSliderIcon);
            canvas.drawPixelMapHolder(mSliderpixelMapHolder, mMaxPosition - mSliderIcon.getImageInfo().size.width / 2F, mMiddleY - mSliderIcon.getImageInfo().size.width / 2F, mPaint);
        } else {
            mSliderFocuspixelMapHolder = new PixelMapHolder(mSliderIconFocus);
            canvas.drawPixelMapHolder(mSliderFocuspixelMapHolder, mMaxPosition - mSliderIcon.getImageInfo().size.width / 2F, mMiddleY - mSliderIcon.getImageInfo().size.width / 2F, mPaint);
        }
    }

    private Rect getTextBounds(String text) {
        mPaint.setTextSize(mNumberTextSize);
        return mPaint.getTextBounds(text);
    }

    private Rect getRulerTextBounds(String text) {
        mPaint.setTextSize(mRulerTextSize);
        return mPaint.getTextBounds(text);
    }

    private void jumpToPosition(int index, TouchEvent event) {

        // user has touched outside the target, lets jump to that position
        if (event.getPointerScreenPosition(index).getX() > mMaxPosition && event.getPointerScreenPosition(index).getX() <= mLineEndX) {
            mMaxPosition = (int) event.getPointerScreenPosition(index).getX();
            invalidate();
            callMaxChangedCallbacks();
        } else if (event.getPointerScreenPosition(index).getX() < mMaxPosition && event.getPointerScreenPosition(index).getX() >= mLineStartX) {
            mMaxPosition = (int) event.getPointerScreenPosition(index).getX();
            invalidate();
            callMaxChangedCallbacks();
        }
    }

    private boolean checkTouchingMinTarget(int index, TouchEvent event) {
        if (isTouchingMinTarget(index, event)) {
            mIsLastTouchedMin = true;
            mTouchingMinTarget.add(event.getPointerId(index));
            return true;
        }
        return false;
    }

    private boolean checkTouchingMaxTarget(int index, TouchEvent event) {
        if (isTouchingMaxTarget(index, event)) {
            mIsLastTouchedMin = false;
            mTouchingMaxTarget.add(event.getPointerId(index));
            return true;
        }
        return false;
    }

    private void callMaxChangedCallbacks() {
        if (mOnSeekBarListener != null) {
            mOnSeekBarListener.onValueChanged(this, getSelectedNumber());
        }
    }

    private boolean isTouchingMinTarget(int pointerIndex, TouchEvent event) {
        return false;
    }

    private boolean isTouchingMaxTarget(int pointerIndex, TouchEvent event) {
        return event.getPointerPosition(pointerIndex).getX() > mMaxPosition - DEFAULT_TOUCH_TARGET_SIZE
            && event.getPointerPosition(pointerIndex).getX() < mMaxPosition + DEFAULT_TOUCH_TARGET_SIZE
            && event.getPointerPosition(pointerIndex).getY() > mMiddleY - DEFAULT_TOUCH_TARGET_SIZE
            && event.getPointerPosition(pointerIndex).getY() < mMiddleY + DEFAULT_TOUCH_TARGET_SIZE;
    }

    private void calculateConvertFactor() {
        mConvertFactor = ((float) mRange) / mLineLength;
    }

    public int getSelectedNumber() {
        return Math.round((mMaxPosition - mLineStartX) * mConvertFactor + mMin);
    }

    /**
     * 设置其他值
     *
     * @param value
     */
    public void setDefaultValue(int value) {
        mIsSelectedNumber = value;
        setSelectedValue(value);
        invalidate();
    }

    private void setSelectedValue(int selectedMax) {
        mMaxPosition = Math.round(((selectedMax - mMin) / mConvertFactor) + mLineStartX);
        callMaxChangedCallbacks();
    }

    public void setOnSeekBarListener(OnSeekBarListener listener) {
        mOnSeekBarListener = listener;
    }

    public int getMin() {
        return mMin;
    }

    /**
     * 设置最小值
     *
     * @param min
     */
    public void setMin(int min) {
        mMin = min;
        mRange = mMax - min;
    }

    /**
     * 获取最大值
     *
     * @return 1
     */
    public int getMax() {
        return mMax;
    }

    /**
     * 设置最大值
     *
     * @param max
     */
    public void setMax(int max) {
        mMax = max;
        mRange = max - mMin;
    }

    /**
     * 设置默认值
     *
     * @param rulerInterval
     */
    public void setInterval(int rulerInterval) {
        mRulerInterval = rulerInterval;
        invalidate();
    }

    /**
     * Resets selected values to MIN and MAX.
     */
    public void reset() {
        mMaxPosition = mLineEndX;
        if (mOnSeekBarListener != null) {
            mOnSeekBarListener.onValueChanged(this, getSelectedNumber());
        }
        invalidate();
    }

    private <T extends Number> T clamp(T value, T min, T max) {
        if (value.doubleValue() > max.doubleValue()) {
            return max;
        } else if (value.doubleValue() < min.doubleValue()) {
            return min;
        }
        return value;
    }

    private int width;
    private int height;

    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = EstimateSpec.getMode(widthMeasureSpec);
        int widthSize = EstimateSpec.getSize(widthMeasureSpec);
        int heightMode = EstimateSpec.getMode(heightMeasureSpec);
        int heightSize = EstimateSpec.getSize(heightMeasureSpec);
        int desiredWidth = widthSize;
        int desiredHeight;
        mMaxTextRect = getTextBounds(String.valueOf(mMax));
        if (mIsShowNumber && mIsShowBubble) {
            desiredHeight = (int) (mSliderIcon.getImageInfo().size.height + mNumberMarginBottom) + mBubbleBitmap.getImageInfo().size.height;
        } else if (mIsShowNumber) {
            desiredHeight = (int) (mSliderIcon.getImageInfo().size.height + mNumberMarginBottom);
        } else {
            desiredHeight = mSliderIcon.getImageInfo().size.height;
        }

        int rulerHeight = (int) (mRulerMarginTop + mRulerDividerHeight * 3 + mRulerTextMarginTop + mRulerTextRect.getHeight());
        if (mIsShowRuler) {
            mRulerTextRect = getRulerTextBounds(String.valueOf(mMin));
            desiredHeight += rulerHeight;
        }

        height = desiredHeight;
        width = widthSize;
        if (widthMode == EstimateSpec.NOT_EXCEED) {
            width = widthSize;
        } else if (widthMode == EstimateSpec.NOT_EXCEED) {
            width = Math.min(desiredWidth, widthSize);
        } else {
            width = desiredWidth;
        }
        height = desiredHeight;
        if (heightMode == EstimateSpec.NOT_EXCEED) {
            height = heightSize;
        } else if (heightMode == EstimateSpec.NOT_EXCEED) {
            height = desiredHeight;
        }

        height += mVerticalPadding;

        int marginStartEnd = mIsShowBubble ? mBubbleBitmap.getImageInfo().size.width : Math.max(mSliderIcon.getImageInfo().size.width, mMaxTextRect.getWidth());

        mLineLength = (width - marginStartEnd);

        mMiddleY = mIsShowRuler ? height - rulerHeight - mSliderIcon.getImageInfo().size.height / 2 :
            height - mSliderIcon.getImageInfo().size.height / 2;
        mLineStartX = marginStartEnd / 2;
        mLineEndX = mLineLength + marginStartEnd / 2;

        calculateConvertFactor();
        if (mIsFirstInit) {
            setSelectedValue(mIsSelectedNumber != -1 ? mIsSelectedNumber : mMax);
        }

        height += mVerticalPadding;
        setEstimatedSize(width, height);
        return false;
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        drawEntireRangeLine(canvas);
        drawSelectedRangeLine(canvas);
        if (mIsShowNumber) {
            drawSelectedNumber(canvas);
        }
        drawRuler(canvas);
        drawSelectedTargets(canvas);
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        if (!isEnabled()) {
            return false;
        }
        mIsFirstInit = false;
        final int actionIndex = touchEvent.getIndex();
        switch (touchEvent.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                if (mIsLastTouchedMin) {
                    if (!checkTouchingMinTarget(actionIndex, touchEvent)
                        && !checkTouchingMaxTarget(actionIndex, touchEvent)) {
                        jumpToPosition(actionIndex, touchEvent);
                    }
                } else if (!checkTouchingMaxTarget(actionIndex, touchEvent)
                    && !checkTouchingMinTarget(actionIndex, touchEvent)) {
                    jumpToPosition(actionIndex, touchEvent);
                }

                invalidate();
                break;

            case TouchEvent.OTHER_POINT_UP:
            case TouchEvent.PRIMARY_POINT_UP:
                mTouchingMinTarget.remove(touchEvent.getPointerId(actionIndex));
                mTouchingMaxTarget.remove(touchEvent.getPointerId(actionIndex));

                invalidate();
                break;

            case TouchEvent.POINT_MOVE:
                for (int i = 0; i < touchEvent.getPointerCount(); i++) {
                    if (mTouchingMinTarget.contains(touchEvent.getPointerId(i))) {
                        int touchX = (int) touchEvent.getPointerPosition(i).getX();
                        touchX = clamp(touchX, mLineStartX, mLineEndX);
                        if (touchX >= mMaxPosition) {
                            mMaxPosition = touchX;
                            callMaxChangedCallbacks();
                        }
                    }
                    if (mTouchingMaxTarget.contains(touchEvent.getPointerId(i))) {
                        int touchX = (int) touchEvent.getPointerPosition(i).getX();
                        touchX = clamp(touchX, mLineStartX, mLineEndX);
                        mMaxPosition = touchX;
                        callMaxChangedCallbacks();
                    }
                }
                invalidate();
                break;

            case TouchEvent.OTHER_POINT_DOWN:
                for (int i = 0; i < touchEvent.getPointerCount(); i++) {
                    if (mIsLastTouchedMin) {
                        if (!checkTouchingMinTarget(i, touchEvent)
                            && !checkTouchingMaxTarget(i, touchEvent)) {
                            jumpToPosition(i, touchEvent);
                        }
                    } else if (!checkTouchingMaxTarget(i, touchEvent)
                        && !checkTouchingMinTarget(i, touchEvent)) {
                        jumpToPosition(i, touchEvent);
                    }
                }

                break;

            case TouchEvent.CANCEL:
                mTouchingMinTarget.clear();
                mTouchingMaxTarget.clear();
                invalidate();
                break;

            default:
                break;
        }

        return true;
    }
}